package map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 计算两个地址之间的距离
 */
public class DistanceCalculator {
    /**
     * 计算两个地址之间的距离
     * 通过调用外部API，将地址转换为经纬度，并计算距离
     *
     * @param address1 第一个地址
     * @param address2 第二个地址
     * @param apiKey   API密钥，用于访问外部服务
     * @return 返回两个地址之间的距离字符串，如果无法找到地址或发生错误，返回相应的提示信息
     */
    public static String geocode(String address1, String address2, String apiKey) {
        try {
            // 计算第一个地址的经纬度
            String origins = getLocationByAddress(address1, apiKey);
            if (origins == null) {
                return null;
            }
            // 计算第二个地址的经纬度
            String destination = getLocationByAddress(address2, apiKey);
            if (destination == null) {
                return null;
            }
            // 计算两个经纬度之间的距离
            String distance = getDistance(origins, destination, apiKey);
            if (distance == null) {
                return null;
            }
            // 构造返回的地址和距离信息
            return distance;
        } catch (Exception e) {
            // 异常处理：打印异常信息，并返回通用错误提示
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据地址获取地理位置信息
     *
     * @param address 需要查询的地址
     * @param apiKey 高德地图API的密钥
     * @return 返回地理位置信息，格式为"经度,纬度"；如果查询失败，返回null
     * @throws Exception 当网络请求失败或其他异常发生时抛出
     */
    private static String getLocationByAddress(String address, String apiKey) throws Exception {
        // 构建高德地图地理编码API的URL字符串
        String urlString = String.format(
                "https://restapi.amap.com/v3/geocode/geo?address=%s&key=%s",
                address, apiKey);
        // 解析发送GET请求得到的JSON响应
        JSONObject responseJson = JSONObject.parseObject(sendGetRequest(urlString));
        // 检查响应状态，如果状态不是"1"，表示请求失败，返回null
        if (!"1".equals(responseJson.getString("status"))) return null;
        // 获取响应中的地理编码数组，并返回第一个地理编码对象的location字段
        JSONArray geocodes = responseJson.getJSONArray("geocodes");
        return geocodes.getJSONObject(0).getString("location");
    }

    /**
     * 计算两个地点之间的距离
     *
     * @param origins 起点的地理位置信息，格式为"经度,纬度"
     * @param destination 终点的地理位置信息，格式为"经度,纬度"
     * @param apiKey 高德地图API的密钥
     * @return 返回两地之间的距离，单位为米；如果查询失败，返回null
     * @throws Exception 当网络请求失败或其他异常发生时抛出
     */
    private static String getDistance(String origins, String destination, String apiKey) throws Exception {
        // 构建高德地图距离计算API的URL字符串
        String urlString = String.format(
                "https://restapi.amap.com/v3/direction/walking?origins=%s&destination=%s&output=json&key=%s",
                origins, destination, apiKey);
        // 解析发送GET请求得到的JSON响应
        JSONObject responseJson = JSONObject.parseObject(sendGetRequest(urlString));
        // 检查响应状态，如果状态不是"1"，表示请求失败，返回null
        if (!"1".equals(responseJson.getString("status"))) {
            return null;
        }
        // 获取响应中的结果数组，并返回第一个结果的distance字段
        JSONArray results = responseJson.getJSONArray("results");
        return results.getJSONObject(0).getString("distance");
    }

    /**
     * 发送GET请求到指定的URL并返回响应内容
     *
     * @param urlString 目标URL字符串
     * @return 返回服务器响应的内容
     * @throws Exception 当网络请求失败或其他异常发生时抛出
     */
    private static String sendGetRequest(String urlString) throws Exception {
        // 创建URL对象
        URL url = new URL(urlString);
        // 打开连接
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // 设置请求方法为GET
        conn.setRequestMethod("GET");
        // 设置连接超时时间和读取超时时间
        conn.setConnectTimeout(5000); // 设置连接超时时间为5秒
        conn.setReadTimeout(5000); // 设置读取超时时间为5秒

        // 读取响应内容
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String inputLine;
        StringBuilder response = new StringBuilder();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        // 返回响应内容
        return response.toString();
    }

    /**
     * 根据给定的地址列表，生成所有可能的地址对，并计算它们之间的距离。
     *
     * @param addresses 地址列表，包含了多个地址字符串。
     * @return 返回一个DistanceResult类型的列表，该列表包含了所有地址对的信息。
     */
    public static List<DistanceResult> generateAddressPairs(List<SysOrganization> addresses){
        return generateAddressPairs(addresses,null);
    }

    /**
     * 根据给定的地址列表，生成所有可能的地址对，排除已经计算过的地址对
     *
     * @param addresses 地址列表，包含了多个地址字符串。
     * @return 返回一个DistanceResult类型的列表，该列表包含了所有地址对的信息。
     */
    public static List<DistanceResult> generateAddressPairs(List<SysOrganization> addresses, List<DistanceResult> excludes){
        excludes = excludes == null ? new ArrayList<>() : excludes;
        //用Stream 转 excludes 为 map模式
        Map<String, DistanceResult> excludesMap = excludes.stream().collect(Collectors.toMap(DistanceResult::connectAddressCode, Function.identity()));
        //排除已经计算过的地址对，即开始地址+结束地址
        List<DistanceResult> results = new ArrayList<>();
        // 外层循环遍历地址列表中的每一个地址，作为起始地址
        for (int i = 0; i < addresses.size(); i++) {
            // 内层循环遍历该地址之后的其他地址，避免重复计算距离
            for (int j = i + 1; j < addresses.size(); j++) {
                // 获取起始地址
                String startAddress = addresses.get(i).getAddress();
                // 获取目标地址
                String destinationAddress = addresses.get(j).getAddress();
                // 创建一个新的DistanceResult对象来保存这对地址的距离信息
                DistanceResult result = new DistanceResult();
                result.setStartAddressCode(addresses.get(i).getOrgCode());
                result.setStartAddress(startAddress);
                result.setDestinationAddressCode(addresses.get(j).getOrgCode());
                result.setDestinationAddress(destinationAddress);
                result.setStatus("0"); //未获取
                result.setUpdateTime(new Date());
                if(!(excludesMap.containsKey(result.connectAddressCode()) || excludesMap.containsKey(result.connectAddressCodeOther()))){
                    // 将新的DistanceResult对象添加到结果列表中
                    results.add(result);
                }
            }
        }
        // 返回包含所有地址对信息的列表
        return results;

    }

    /**
     * 计算并更新地址对之间的距离结果
     *
     * 该方法接收一个包含距离结果的列表和一个API密钥，对列表中的每个距离结果，
     * 调用地理编码服务计算始地址和目的地址之间的距离，并更新距离结果的距
     * 离值和更新时间如果计算距离时发生异常，则打印错误信息
     *
     * @param addresses 包含始地址和目的地址的距离结果列表
     * @param apiKey 调用地理编码服务所需的API密钥
     * @return 返回更新了距离值和更新时间的距离结果列表
     */
    public static List<DistanceResult> generateDistanceResults(List<DistanceResult> addresses, String apiKey) {
        // 遍历地址列表，对每个地址对计算距离
        addresses.forEach(address -> {
            try {
                // 调用地理编码服务获取距离
                String distance = geocode(address.getStartAddress(), address.getDestinationAddress(), apiKey);
                address.setStatus(StringUtils.isBlank(distance)?"1":"2"); //0-未开始获取距离, 1-未获取距离成功, 2-获取距离成功
                // 更新距离结果的距离值和更新时间
                address.setDistance(distance); //单位 米
                address.setUpdateTime(new Date());
            } catch (Exception e) {
                // 如果计算距离时发生异常，打印错误信息
                System.err.println("计算距离失败: " + e.getMessage());
            }
        });
        // 返回更新后的地址列表
        return addresses;
    }

    public static void main(String[] args) {
//        String address1 = "九江银行江西省南昌市进贤县支行";
//        String address2 = "九江银行江西省南昌市高新支行";
//        String apiKey = "338dc1d7447621bc8bf002008abb59d4"; // 使用你的高德地图API密钥替换
//        System.out.println(geocode(address1, address2, apiKey));

        List<SysOrganization> addresses = new ArrayList<>();
        SysOrganization sysOrganization = new SysOrganization();
        sysOrganization.setOrgCode("1");
        sysOrganization.setOrgName("九江银行江西省南昌市进贤县支行");
        sysOrganization.setAddress("南昌市进贤县胜利中路81号");
        addresses.add(sysOrganization);

        SysOrganization sysOrganization1 = new SysOrganization();
        sysOrganization1.setOrgCode("2");
        sysOrganization1.setOrgName("九江银行江西省南昌市高新支行");
        sysOrganization1.setAddress("南昌市火炬大街186号");
        addresses.add(sysOrganization1);

        SysOrganization sysOrganization2 = new SysOrganization();
        sysOrganization2.setOrgCode("3");
        sysOrganization2.setOrgName("九江银行江西省南昌市洪都大道支行");
        sysOrganization2.setAddress("南昌市青山湖区洪都大道183号");
        addresses.add(sysOrganization2);

        SysOrganization sysOrganization3 = new SysOrganization();
        sysOrganization3.setOrgCode("4");
        sysOrganization3.setOrgName("九江银行江西省南昌市洪城支行");
        sysOrganization3.setAddress("南昌市洪城路6号");
        addresses.add(sysOrganization3);

        List<DistanceResult> excludes = new ArrayList<>();
        DistanceResult distanceResult = new DistanceResult();
        distanceResult.setStartAddress("南昌市进贤县胜利中路81号");
        distanceResult.setStartAddressCode("1");
        distanceResult.setDestinationAddressCode("2");
        distanceResult.setDistance("1000");
        distanceResult.setUpdateTime(new Date());
        excludes.add(distanceResult);

        DistanceResult distanceResult1 = new DistanceResult();
        distanceResult1.setStartAddressCode("2");
        distanceResult1.setDestinationAddressCode("1");
        distanceResult1.setDistance("1000");
        distanceResult1.setUpdateTime(new Date());
        excludes.add(distanceResult1);

        String apiKey = "602d3f2d00bbb12a25eb579bd4125f06"; // 使用环境变量获取API密钥
        List<DistanceResult> results = generateDistanceResults(generateAddressPairs(addresses), apiKey);
        for (DistanceResult result : results) {
            System.out.println(result);
        }
    }
}
